package com.health.ts.utils;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.xml.namespace.QName;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;

/**
 * 
 * Created on 2019-06-12
 * <p>
 * Description WebService工具类
 * <p>
 */
public class WebServiceUtils {

	/**
	 * log
	 */
	private static Logger log = LoggerFactory.getLogger(WebServiceUtils.class);

	/**
	 * 缓存最大限制
	 */
	private static final int DEFAULT_MAX_SIZE = 100;

	/**
	 * 对写锁
	 */
	private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

	/**
	 * 解决客户端创建时间长导致读锁定bug，进而导致read time out 使用LRUCache同时解决不同服务client创建的阻塞问题
	 */
	private static LRUCache<String, Lock> lockKeyCache = new LRUCache<String, Lock>(DEFAULT_MAX_SIZE);

	/**
	 * client缓存
	 */
	private static LRUCache<String, Client> cache = new LRUCache<String, Client>(DEFAULT_MAX_SIZE);

	private WebServiceUtils() {

	}

	/**
	 * cxf方式动态调用
	 * 
	 * @param wsdl
	 * @param targetnamespace
	 * @param method
	 * @param args
	 * @return
	 */
	public static String wsClient(String wsdl, String targetnamespace, String method, Object... args) throws Exception {
		log.debug("开始创建客户端wsClient");
		Client client = createClient(wsdl, targetnamespace, method);
		log.debug("完毕创建客户端wsClient，开始调用");
		return invoke(client, targetnamespace, method, args);
	}

	/**
	 * cxf方式动态调用
	 * 
	 * @param wsdl
	 * @param targetnamespace
	 * @param method
	 * @param timeout
	 * @param args
	 * @return
	 * @throws CustomException
	 */
	public static String wsClientWithTimeOut(String wsdl, String targetnamespace, String method, long timeout, Object... args) throws Exception {
		log.debug("开始创建客户端wsClientWithTimeOut");
		Client client = createClient(wsdl, targetnamespace, method, timeout);
		log.debug("完毕创建客户端wsClientWithTimeOut，开始调用");
		return invoke(client, targetnamespace, method, args);
	}

	/**
	 * 设置超时时间
	 * 
	 * @param client
	 * @param timeout
	 */
	private static void setTimeout(Client client, long timeout) {
		HTTPConduit http = (HTTPConduit) client.getConduit();
		HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
		// httpClientPolicy.setConnectionTimeout(connTimeOut);
		httpClientPolicy.setAllowChunking(false);
		httpClientPolicy.setReceiveTimeout(timeout);
		http.setClient(httpClientPolicy);
	}

	/**
	 * 执行
	 * 
	 * @param client
	 * @param targetnamespace
	 * @param method
	 * @param args
	 * @return
	 * @throws Exception
	 */
	private static String invoke(Client client, String targetnamespace, String method, Object... args) throws Exception {
		QName opName = new QName(targetnamespace, method); // 指定到接口类所在包
		log.debug("WebService Client开始调用");
		Object[] objs = client.invoke(opName, args);
		return objs[0].toString();
	}

	/**
	 * @param wsdl
	 * @param targetnamespace
	 * @param method
	 * @return
	 */
	private static Client createClient(String wsdl, String targetnamespace, String method) {
		return createClient(wsdl, targetnamespace, method, 0);
	}

	/**
	 * @param wsdl
	 * @param targetnamespace
	 * @param method
	 * @return
	 */
	private static Client createClient(String wsdl, String targetnamespace, String method, long timeout) {
		String key = createKey(wsdl, targetnamespace, method, timeout);

		readWriteLock.readLock().lock();
		if (!cache.containsKey(key)) {
			readWriteLock.readLock().unlock();

			// 根据服务key创建或重用锁
			Lock lock = lock(key);
			boolean hasWriteLock = false;
			try {
				if (!cache.containsKey(key)) {
					Client client = doCreateClinet(wsdl, timeout);

					// 解决创建client超时引起read加锁bug
					readWriteLock.writeLock().lock();
					hasWriteLock = true;
					cache.put(key, client);
				}
				readWriteLock.readLock().lock();
			} finally {
				if (hasWriteLock) {
					readWriteLock.writeLock().unlock();
				}
				// 释放锁
				unlock(key, lock);
			}
		}

		try {
			return cache.get(key);
		} finally {
			readWriteLock.readLock().unlock();
		}
//		log.info("xpm创建客户端，没有锁和缓存");
//		return doCreateClinet(wsdl, timeout);
	}

	/**
	 * Created on 2017年9月13日 .实际动态生成client
	 * 
	 * @author Administrator
	 * @param wsdl
	 * @param timeout
	 * @return {描述}
	 */
	private static Client doCreateClinet(String wsdl, long timeout) {
		ClassLoader cl = ClassUtils.getDefaultClassLoader();
		try {
			JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
			Client client = dcf.createClient(wsdl);
			if (timeout != 0) {
				setTimeout(client, timeout);
			}
			return client;
		}catch (Exception e) {
			e.printStackTrace();
		} finally {
			Thread.currentThread().setContextClassLoader(cl);
		}
		return null;
	}

	/**
	 * 根据服务key创建或重用锁
	 * 
	 * @param key
	 */
	private static Lock lock(String key) {
		Lock lock = getKeyLock(key);
		lock.lock();
		return lock;
	}

	/**
	 * 释放锁
	 * 
	 * @param key
	 */
	private static void unlock(String key, Lock lock) {
		if (lockKeyCache.containsKey(key)) {
			synchronized (lockKeyCache) {
				if (lockKeyCache.containsKey(key)) {
					lockKeyCache.remove(key);
				}
			}
		}
		lock.unlock();
	}

	/**
	 * 创建或重用锁
	 * 
	 * @param key
	 * @return
	 */
	private static Lock getKeyLock(String key) {
		Lock lock = lockKeyCache.get(key);
		if (lock == null) {
			synchronized (lockKeyCache) {
				if (lockKeyCache.containsKey(key)) {
					lock = lockKeyCache.get(key);
				} else {
					lock = new ReentrantLock();
					lockKeyCache.put(key, lock);
				}
			}
		}
		return lock;
	}

	/**
	 * @param wsdl
	 * @param targetnamespace
	 * @param method
	 * @param timeout
	 * @return
	 */
	private static String createKey(String wsdl, String targetnamespace, String method, long timeout) {
		return String.format("%s%s%s%s", wsdl, targetnamespace, method, timeout);
	}
	
	/**
	 * 可设置最大容量的缓存map
	 *
	 * @param <K>
	 * @param <V>
	 */
	private static class LRUCache<K, V> extends LinkedHashMap<K, V> {

		/**
		 * serialVersionUID
		 */
		private static final long serialVersionUID = 1L;
		
		/**
		 * maxSize
		 */
		private final int maxSize;

		public LRUCache(int maxSize) {
			this(maxSize, 16, 0.75f, false);
		}

		public LRUCache(int maxSize, int initialCapacity, float loadFactor, boolean accessOrder) {
			super(initialCapacity, loadFactor, accessOrder);
			this.maxSize = maxSize;
		}

		protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
			return this.size() > this.maxSize;
		}
	}
}